#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/async_tx.h>
#include <linux/slab.h>
#include <linux/mm.h>

#include <asm-generic/errno-base.h>
#include <uapi/asm-generic/errno.h>
#include <linux/fb.h>

#include "ili9488cmds.h"
#include "ili9488_regs.h"

#define DEBUG_BACKLIGHT             (1<<17)
#define DEBUG_READ                  (1<<18)
#define DEBUG_WRITE                 (1<<19)
#define DEBUG_WRITE_VMEM            (1<<20)
#define DEBUG_WRITE_REGISTER        (1<<21)
#define DEBUG_SET_ADDR_WIN          (1<<22)
#define DEBUG_RESET                 (1<<23)
#define DEBUG_MKDIRTY               (1<<24)
#define DEBUG_UPDATE_DISPLAY        (1<<25)
#define DEBUG_INIT_DISPLAY          (1<<26)
#define DEBUG_BLANK                 (1<<27)
#define DEBUG_REQUEST_GPIOS         (1<<28)
#define DEBUG_FREE_GPIOS            (1<<29)
#define DEBUG_REQUEST_GPIOS_MATCH   (1<<30)
#define DEBUG_VERIFY_GPIOS          (1<<31)

#define WIDTH 480
#define HEIGHT 320

#define  RED  (BIT(15) | BIT(14) | BIT(13) | BIT(12) | BIT(11))
#define  GREEN  (BIT(10) | BIT(9) | BIT(8) | BIT(7) | BIT(6) | BIT(5))
#define  BLUE  (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4))
#define  BLACK  0
#define  WHITE    ( RED|GREEN | BLUE )

#define  TX_BUFFER_SIZE  (460800)

#define  DEBUG1  0

#define fbtft_dev_dbg(level, par, dev, format, arg...)       \
do {                                                         \
		dev_info(dev, format, ##arg);                \
} while (0)

#define fbtft_par_dbg(level, par, format, arg...)            \
do {                                                         \
	if (unlikely(par->debug & level))                    \
		dev_info(par->info->device, format, ##arg);  \
} while (0)

//#define dc_rs01(a) gpiod_direction_output(dc_rs,a)
//
//#define reset01(a) gpiod_direction_output(reset,a)
//
//#define cs(a) gpiod_set_value(dc, a)

//#define   PAGE_SIZE  "device name 499"

/**
 * When you have only one slave, then you can directly connect the SS/CS pin to the ground if your slave permits it.
 *
 * https://embetronicx.com/tutorials/tech_devices/spi-serial-peripheral-interface-protocol-basics/
 *
 * https://www.cnblogs.com/tansuoxinweilai/p/11405029.html
 *
 * https://github.com/gmtii/ili9341-arduino/blob/master/TFTv2.cpp
 *
 * https://github.com/CariadDisplayLibrary/ILI9342/blob/master/src/ILI9342.cpp
 *
 * https://github.com/jks-liu/ili9341/blob/master/ili9341.c
 *
 * https://github.com/Bodmer/TFT_ILI9341/tree/master/examples
 *
 * https://blog.csdn.net/jklinux/article/details/79612831
 *
 *
 *
 *
 *
 *reset-gpios
 Hardware reset
 return 0;


 dc-gpios
 Data/Command (sometimes called RS)

 led-gpios
 Backlight


 ilitek,ili9341



 Bit lengths used by the RGB encoding (5+6+5=16 in this case) is called the color depth,
 which is used by the frame buffer console driver to choose the logo file to display during boot (see the section "Boot Logo").

 fbset  -fb /dev/fb1



 led   - 3.5v
 reset  --  board  38
 dc  -- board 35



 *
 */

#define DISPLAY_OFF_CMD   _IOW('A',0x28,unsigned int)
#define DISPLAY_ON_CMD   _IOW('A',0x29,unsigned int)

int tftfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) ;

struct fbtft_par {
	struct spi_device *spi;
	struct platform_device *pdev;

	struct {
		void *buf;
		dma_addr_t dma;
		size_t len;
	} txbuf;

	struct {
		struct gpio_desc *reset;
		struct gpio_desc *dc;
	} gpio;

	struct fb_info *info;

	spinlock_t dirty_lock;
	unsigned dirty_lines_start;
	unsigned dirty_lines_end;

	bool first_update_done;
	struct timespec update_time;

	unsigned long debug;
};

struct fbtft_par *par;

static struct spi_device *my_spi_device;

struct gpio_desc *led;

u8 *buffer;

struct fb_info *info;

void fbtft_set_addr_win(struct fbtft_par *par, int x_start, int y_start,
		int x_end, int y_end);

int fbtft_write_vmem16_bus8(struct device *dev, struct fbtft_par *par,
		size_t offset, size_t len);

int myfb_open(struct fb_info *info, int user) {
	dev_info(info->dev, "myfb_open\n");
	return 0;
}
int myfb_release(struct fb_info *info, int user) {
	dev_info(info->dev, "myfb_release\n");
	return 0;
}
int myfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) {
	dev_info(info->dev, "myfb_check_var\n");
	return 0;
}

/* set the video mode according to info->var */
int myfb_set_par(struct fb_info *info) {
	dev_info(info->dev, "myfb_set_par\n");
	return 0;
}

int myfb_blank(int blank, struct fb_info *info) {
	dev_info(info->dev, "fb_blank: %d\n", blank);
	return 0;
}

/* set color register */
int myfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue,
		unsigned transp, struct fb_info *info) {
	dev_info(info->dev, "myfb_setcolreg\n");
	return 0;
}

void fbtft_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) {
//	struct fbtft_par *par = info->par;

	fbtft_dev_dbg(DEBUG_FB_FILLRECT, par, info->dev,
			"%s: dx=%d, dy=%d, width=%d, height=%d\n", __func__, rect->dx,
			rect->dy, rect->width, rect->height);
	sys_fillrect(info, rect);

//	par->fbtftops.mkdirty(info, rect->dy, rect->height);
}

void fbtft_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) {
//	struct fbtft_par *par = info->par;

	fbtft_dev_dbg(DEBUG_FB_COPYAREA, par, info->dev,
			"%s: dx=%d, dy=%d, width=%d, height=%d\n", __func__, area->dx,
			area->dy, area->width, area->height);
	sys_copyarea(info, area);

//	par->fbtftops.mkdirty(info, area->dy, area->height);
}

void fbtft_fb_imageblit(struct fb_info *info, const struct fb_image *image) {
//	struct fbtft_par *par = info->par;

	fbtft_dev_dbg(DEBUG_FB_IMAGEBLIT, par, info->dev,
			"%s: dx=%d, dy=%d, width=%d, height=%d\n", __func__, image->dx,
			image->dy, image->width, image->height);
	sys_imageblit(info, image);

//	par->fbtftops.mkdirty(info, image->dy, image->height);
}



struct fb_ops myfb_ops = { .owner = THIS_MODULE, .fb_read = fb_sys_read,
		.fb_release = myfb_release, .fb_check_var = myfb_check_var,
		.fb_set_par = myfb_set_par, .fb_blank = myfb_blank, .fb_setcolreg =
				myfb_setcolreg,/* Set color map */
		.fb_ioctl = tftfb_ioctl, .fb_fillrect = fbtft_fb_fillrect,
		.fb_copyarea = fbtft_fb_copyarea, /* Generic function to copy area */
		.fb_imageblit = fbtft_fb_imageblit, /* Generic function to draw */
};

static int initialize_fb_info(struct fb_info *info, struct device *dev) {
	unsigned bpp = 16;
	int vmem_size = WIDTH * HEIGHT * bpp / 8;
	u8 *vmem;
	vmem = vzalloc(vmem_size);
	if (!vmem)
		goto alloc_fail;

	info->screen_base = (u8 __force __iomem*) vmem;

	strncpy(info->fix.id, dev->driver->name, 16);
	info->fix.type = FB_TYPE_PACKED_PIXELS;
	info->fix.visual = FB_VISUAL_TRUECOLOR;
	info->fix.xpanstep = 0;
	info->fix.ypanstep = 0;
	info->fix.ywrapstep = 0;
	info->fix.line_length = WIDTH * bpp / 8;
	info->fix.accel = FB_ACCEL_NONE;
	info->fix.smem_len = vmem_size;

	info->var.rotate = 0;
	info->var.xres = WIDTH;
	info->var.yres = HEIGHT;
	info->var.xres_virtual = info->var.xres;
	info->var.yres_virtual = info->var.yres;
	info->var.bits_per_pixel = bpp;
	info->var.nonstd = 1;

	/* RGB565 */
	info->var.red.offset = 11;
	info->var.red.length = 5;
	info->var.green.offset = 5;
	info->var.green.length = 6;
	info->var.blue.offset = 0;
	info->var.blue.length = 5;
	info->var.transp.offset = 0;
	info->var.transp.length = 0;
	info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
	return 0;
	alloc_fail: return -1;

}

inline void fbtft_update_display(struct device *dev, struct fbtft_par *par,
		unsigned start_line, unsigned end_line) {
	size_t offset, len;
	struct timespec ts_start, ts_end, ts_fps, ts_duration;
	long fps_ms, fps_us, duration_ms, duration_us;
	long fps, throughput;
	bool timeit = false;
	int ret = 0;

	pr_err("fbtft_update_display\n");

	getnstimeofday(&ts_start);
	timeit = true;

	/* Sanity checks */
	if (start_line > end_line) {
		dev_warn(par->info->device,
				"%s: start_line=%u is larger than end_line=%u. Shouldn't happen, will do full display update\n",
				__func__, start_line, end_line);
		start_line = 0;
		end_line = par->info->var.yres - 1;
	}
	if (start_line > par->info->var.yres - 1
			|| end_line > par->info->var.yres - 1) {
		dev_warn(par->info->device,
				"%s: start_line=%u or end_line=%u is larger than max=%d. Shouldn't happen, will do full display update\n",
				__func__, start_line, end_line, par->info->var.yres - 1);
		start_line = 0;
		end_line = par->info->var.yres - 1;
	}

	fbtft_par_dbg(DEBUG_UPDATE_DISPLAY, par, "%s(start_line=%u, end_line=%u)\n",
			__func__, start_line, end_line);

	fbtft_set_addr_win(par, 0, start_line, par->info->var.xres - 1, end_line);

	offset = start_line * par->info->fix.line_length;
	len = (end_line - start_line + 1) * par->info->fix.line_length;
	ret = fbtft_write_vmem16_bus8(dev, par, offset, len);
	if (ret < 0)
		dev_err(par->info->device,
				"%s: write_vmem failed to update display buffer\n", __func__);

	if (unlikely(timeit)) {
		getnstimeofday(&ts_end);
		if (par->update_time.tv_nsec == 0 && par->update_time.tv_sec == 0) {
			par->update_time.tv_sec = ts_start.tv_sec;
			par->update_time.tv_nsec = ts_start.tv_nsec;
		}
		ts_fps = timespec_sub(ts_start, par->update_time);
		par->update_time.tv_sec = ts_start.tv_sec;
		par->update_time.tv_nsec = ts_start.tv_nsec;
		fps_ms = (ts_fps.tv_sec * 1000) + ((ts_fps.tv_nsec / 1000000) % 1000);
		fps_us = (ts_fps.tv_nsec / 1000) % 1000;
		fps = fps_ms * 1000 + fps_us;
		fps = fps ? 1000000 / fps : 0;

		ts_duration = timespec_sub(ts_end, ts_start);
		duration_ms = (ts_duration.tv_sec * 1000)
				+ ((ts_duration.tv_nsec / 1000000) % 1000);
		duration_us = (ts_duration.tv_nsec / 1000) % 1000;
		throughput = duration_ms * 1000 + duration_us;
		throughput = throughput ? (len * 1000) / throughput : 0;
		throughput = throughput * 1000 / 1024;

		dev_info(par->info->device,
				"Display update: %ld kB/s (%ld.%.3ld ms), fps=%ld (%ld.%.3ld ms)\n",
				throughput, duration_ms, duration_us, fps, fps_ms, fps_us);
		par->first_update_done = true;
	}
}

void fbtft_mkdirty(struct fb_info *info, int y, int height) {
	struct fbtft_par *par = info->par;
	struct fb_deferred_io *fbdefio = info->fbdefio;

	/* special case, needed ? */
	if (y == -1) {
		y = 0;
		height = info->var.yres - 1;
	}

	/* Mark display lines/area as dirty */
	spin_lock(&par->dirty_lock);
	if (y < par->dirty_lines_start)
		par->dirty_lines_start = y;
	if (y + height - 1 > par->dirty_lines_end)
		par->dirty_lines_end = y + height - 1;
	spin_unlock(&par->dirty_lock);

	/* Schedule deferred_io to update display (no-op if already on queue)*/
	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}

void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) {
	struct fbtft_par *par = info->par;
	unsigned dirty_lines_start, dirty_lines_end;
	struct page *page;
	unsigned long index;
	unsigned y_low = 0, y_high = 0;
	int count = 0;
	BUG_ON(!par);

	pr_err("fbtft_deferred_io start\n");

	spin_lock(&par->dirty_lock);
	dirty_lines_start = par->dirty_lines_start;
	dirty_lines_end = par->dirty_lines_end;
	pr_err("fbtft_deferred_io %d\n", __LINE__);
	/* set display line markers as clean */
	par->dirty_lines_start = par->info->var.yres - 1;
	par->dirty_lines_end = 0;
	spin_unlock(&par->dirty_lock);

	pr_err("fbtft_deferred_io %d\n", __LINE__);

	/* Mark display lines as dirty */
	list_for_each_entry(page, pagelist, lru)
	{
//		pr_err("fbtft_deferred_io list_for_each_entry\n");
		count++;
		index = page->index << PAGE_SHIFT;
		y_low = index / info->fix.line_length;
		y_high = (index + PAGE_SIZE - 1) / info->fix.line_length;
//		fbtft_dev_dbg(DEBUG_DEFERRED_IO, par, info->device,
//				"page->index=%lu y_low=%d y_high=%d\n", page->index, y_low,
//				y_high);
		if (y_high > info->var.yres - 1)
			y_high = info->var.yres - 1;
		if (y_low < dirty_lines_start)
			dirty_lines_start = y_low;
		if (y_high > dirty_lines_end)
			dirty_lines_end = y_high;
	}

	fbtft_update_display(info->dev, info->par, dirty_lines_start,
			dirty_lines_end);
	pr_err("fbtft_deferred_io start\n");
}

inline void init_fb(struct device *dev) {
	struct fb_deferred_io *fbdefio = NULL;
	info = framebuffer_alloc(0, dev);
	info->fbops = &myfb_ops;
	info->par = par;
	par->info = info;
	fbdefio = devm_kzalloc(dev, sizeof(struct fb_deferred_io), GFP_KERNEL);
	fbdefio->delay = HZ / 60;
	fbdefio->deferred_io = fbtft_deferred_io;
	info->fbdefio = fbdefio;
	fb_deferred_io_init(info);

	fb_alloc_cmap(&info->cmap, 16, 0);
	initialize_fb_info(info, dev);

	myfb_set_par(info);
	register_framebuffer(info);
}

inline void Lcd_Write_Com(u8 cmd);

inline void Lcd_Write_Data2(u8 *data, size_t len);

void fbtft_write_reg8_bus8(struct fbtft_par *par, int len, ...) {
	int i;
	int para;
	va_list args;
	va_start(args, len);
	para = va_arg(args, int);
	Lcd_Write_Com(para);
//	pr_err("first arg  %x \n",para);

	for (i = 0; i < len - 1; i++) {
		para = va_arg(args, int);
		*(buffer + i) = para;
	}

	print_hex_dump(KERN_DEBUG, "fbtft_write_reg8_bus8: ", DUMP_PREFIX_NONE, 2,
			1, buffer, len - 1, false);

	va_end(args);

	if (len - 1 > 0) {
		Lcd_Write_Data2(buffer, len - 1);
	}
//	pr_err("write_reg end ###################################");
}

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

#define write_reg(par, ...)  \
do {                     \
	fbtft_write_reg8_bus8(par, NUMARGS(__VA_ARGS__), __VA_ARGS__);   \
} while (0)

inline int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len) {
	int ret;
	struct spi_transfer t = { .tx_buf = buf, .len = len, };
	struct spi_message m;

	pr_err("fbtft_write_spi(len = %ld)\n", len);

	if (!par->spi) {
		pr_err("spi is NULL\n");
		return -1;
	}
	spi_message_init(&m);
	t.tx_dma = par->txbuf.dma;
	m.is_dma_mapped = 1;
	spi_message_add_tail(&t, &m);
//	pr_err("fbtft_write_spi end\n");
	ret = spi_sync(par->spi, &m);
	if (ret == 0) {
//		pr_err("fbtft_write_spi(len=%ld),success \n", len);
	} else {
		pr_err("fbtft_write_spi(len=%ld),fail \n", len);
	}
	return ret;
}

/* 16 bit pixel over 8-bit databus */
inline int fbtft_write_vmem16_bus8(struct device *dev, struct fbtft_par *par,
		size_t offset, size_t len) {
	u16 *vmem16;
	u8 *txbuf = par->txbuf.buf;
	size_t remain;
	size_t to_copy;
	size_t tx_array_size;
	int i;
	int ret = 0;

	pr_err("len = %ld  \n", len);

	/* remaining number of pixels to send */
	remain = len / 2;
	vmem16 = (u16*) (par->info->screen_buffer + offset);

	gpiod_set_raw_value(par->gpio.dc, 1);

	/* number of pixels that fits in the transmit buffer */
	tx_array_size = par->txbuf.len / 3;

	while (remain) {
		pr_err("line =  %dL\n", __LINE__);
		/* number of pixels to copy in one iteration of the loop */
		to_copy = min(tx_array_size, remain);
//		dev_dbg(par->info->device, "    to_copy=%zu, remain=%zu\n",
//						to_copy, remain - to_copy);

		for (i = 0; i < to_copy; i++) {
			u16 pixel = vmem16[i];
			u16 b = pixel & 0x1f;
			u16 g = (pixel & (0x3f << 5)) >> 5;
			u16 r = (pixel & (0x1f << 11)) >> 11;

			u8 r8 = (r & 0x1F) << 3;
			u8 g8 = (g & 0x3F) << 2;
			u8 b8 = (b & 0x1F) << 3;

			txbuf[i * 3 + 0] = r8;
			txbuf[i * 3 + 1] = g8;
			txbuf[i * 3 + 2] = b8;
		}
//		pr_err("to_copy %ld  \n", to_copy * 3);
		vmem16 += to_copy;
		pr_err("line =  %d\n", __LINE__);
		ret = fbtft_write_spi(par, par->txbuf.buf, to_copy * 3);
		if (ret < 0)
			return ret;

		remain -= to_copy;
	}
	return ret;
}

/* 16 bit pixel over 8-bit databus */
inline int fbtft_write_vmem16_bus82(struct device *dev, struct fbtft_par *par,
		u16 color, size_t offset, size_t len) {
	u16 pixel = color;
	u16 b = pixel & 0x1f;
	u16 g = (pixel & (0x3f << 5)) >> 5;
	u16 r = (pixel & (0x1f << 11)) >> 11;

	u8 r8 = (r & 0x1F) << 3;
	u8 g8 = (g & 0x3F) << 2;
	u8 b8 = (b & 0x1F) << 3;

	u8 *txbuf = par->txbuf.buf;
	size_t remain;
	size_t to_copy;
	size_t tx_array_size;
	int i;
	int ret = 0;

	pr_err("len = %ld  \n", len);

	remain = len;
	gpiod_set_raw_value(par->gpio.dc, 1);
	tx_array_size = par->txbuf.len / 3;

	while (remain) {
		to_copy = min(tx_array_size, remain);
		for (i = 0; i < to_copy; i++) {
			txbuf[i * 3 + 0] = r8;
			txbuf[i * 3 + 1] = g8;
			txbuf[i * 3 + 2] = b8;
		}
//		pr_err("to_copy %ld  \n", to_copy * 3);
		ret = fbtft_write_spi(par, par->txbuf.buf, to_copy * 3);
		if (ret < 0)
			return ret;
		remain -= to_copy;
	}
	return ret;
}

inline void lcd_write(u8 *data) {
	fbtft_write_spi(par, data, 1);
}

inline void lcd_write2(u8 *data, size_t len) {
	fbtft_write_spi(par, data, len);
}

inline void Lcd_Write_Com(u8 cmd) {
	if (DEBUG1)
		pr_err("Lcd_Write_Com = 0x%x\n", cmd);
	gpiod_set_raw_value(par->gpio.dc, 0);
	lcd_write(&cmd);
}

inline void Lcd_Write_Data(u8 data) {
	gpiod_set_raw_value(par->gpio.dc, 1);
	lcd_write(&data);
}

inline void Lcd_Write_Data2(u8 *data, size_t len) {
	gpiod_set_raw_value(par->gpio.dc, 1);
	lcd_write2(data, len);
}

inline void lcd_write_cmd_data(u8 cmd, u8 data) {
	Lcd_Write_Com(cmd);
	Lcd_Write_Data(data);
}

inline void fbtft_set_addr_win(struct fbtft_par *par, int x_start, int y_start,
		int x_end, int y_end) {
	/* Column address set */
	write_reg(par, ILI9488_CMD_COLUMN_ADDRESS_SET, (x_start >> 8) & 0xFF,
			x_start & 0xFF, (x_end >> 8) & 0xFF, x_end & 0xFF);

	/* Row adress set */
	write_reg(par, ILI9488_CMD_PAGE_ADDRESS_SET, (y_start >> 8) & 0xFF,
			y_start & 0xFF, (y_end >> 8) & 0xFF, y_end & 0xFF);

	/* Memory write */
	write_reg(par, ILI9488_CMD_MEMORY_WRITE);
}

inline int lcd_clear(struct device *dev, u16 color) {
	size_t offset, len;
	int ret = 0;
	offset = 0;
	len = WIDTH * HEIGHT;
	fbtft_set_addr_win(par, 0, 0, WIDTH - 1, HEIGHT - 1);
	ret = fbtft_write_vmem16_bus82(dev, par, color, offset, len);
	return ret;
}

inline int draw_rect(struct device *dev, u16 color, int start_x, int start_y,
		int width, int height) {
	size_t offset, len;
	int ret = 0;
	offset = 0;
	len = width * height;
	fbtft_set_addr_win(par, start_x, start_y, start_x + width - 1,
			start_y + height - 1);
	ret = fbtft_write_vmem16_bus82(dev, par, color, offset, len);
	return ret;
}

inline int draw_hline(struct device *dev, u16 color, int start_x, int start_y,
		int width) {
	return draw_rect(dev, color, start_x, start_y, width, 1);
}

inline int draw_vline(struct device *dev, u16 color, int start_x, int start_y,
		int height) {
	return draw_rect(dev, color, start_x, start_y, 1, height);
}

inline void fbtft_reset(struct fbtft_par *par) {
	gpiod_set_raw_value(par->gpio.reset, 0);
	udelay(20);
	gpiod_set_raw_value(par->gpio.reset, 1);
	mdelay(120);
}

inline int init_display(void) {
	//reset
	fbtft_reset(par);

	/* startup sequence for MI0283QT-9A */
	write_reg(par, ILI9488_CMD_SOFTWARE_RESET); /* software reset */
	mdelay(5);
	write_reg(par, ILI9488_DISPOFF); /* display off */

	write_reg(par, ILI9488_CMD_POSITIVE_GAMMA_CTRL, 0x00, 0x03, 0x09, 0x08,
			0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F);

	write_reg(par, ILI9488_CMD_NEGATIVE_GAMMA_CTRL, 0x00, 0x16, 0x19, 0x03,
			0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F);

	write_reg(par, ILI9488_CMD_POWER_CONTROL_1, 0x17, 0x15);
	write_reg(par, ILI9488_CMD_POWER_CONTROL_2, 0x41);

	write_reg(par, ILI9488_CMD_VCOM_CONTROL_1, 0x00, 0x12, 0x80);

	write_reg(par, ILI9488_CMD_MEMORY_ACCESS_CONTROL, 0x48);

	write_reg(par, ILI9488_CMD_INTERFACE_PIXEL_FORMAT, BIT(2) | BIT(1));

	write_reg(par, ILI9488_CMD_INTERFACE_MODE_CONTROL, 0x80);

	write_reg(par, ILI9488_CMD_FRAME_RATE_CONTROL_NORMAL, 0xA0);

	write_reg(par, ILI9488_CMD_DISPLAY_INVERSION_CONTROL, 0x02);
	write_reg(par, ILI9488_CMD_DISPLAY_FUNCTION_CONTROL, 0x02, 0x02);

	write_reg(par, ILI9488_CMD_SET_IMAGE_FUNCTION, 0x00);

	write_reg(par, ILI9488_CMD_ADJUST_CONTROL_3, 0xA9, 0x51, 0x2C, 0x82);

	write_reg(par, ILI9488_CMD_SLEEP_OUT);
	mdelay(120);

	write_reg(par, ILI9488_DISPON);
	mdelay(20);
	return 0;
}

void dma_complete_callback(void *dma_async_param) {
	pr_err("dma_callback \n");
}

bool dma_filter(struct dma_chan *chan, void *filter_param) {
//	pr_err("dma_chan_id = %d \n", chan->device->dev_id);
//	pr_err("dma_chan_id = %d \n", chan->chan_id);
//	pr_err("dma_chan_name = %s \n", dma_chan_name(chan));
//	pr_err("dma_filter_fn \n");
//	return strcmp(dma_chan_name(chan), "dma0chan2") == 0;
	return true;
}

/* perform fb specific ioctl (optional) */
int tftfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) {
	pr_err("arg %ld\n", arg);
	write_reg(par, _IOC_NR(cmd));
	return 0;
}

struct dma_chan *chan;

struct device *dev;

int my_probe(struct spi_device *spi) {
	int ret = 0;
	dma_cap_mask_t mask;
	my_spi_device = spi;
	dev = &spi->dev;

	dma_cap_zero(mask);
	dma_cap_set(DMA_SLAVE, mask);
	chan = dma_request_channel(mask, dma_filter, NULL);
	if (!chan) {
		dev_info(dev, "dma_request_channel failed\n");
		return -1;
	} else {
		dev_info(dev, "dma_request_channel ok\n");
	}
	pr_err("dma_chan_name %s \n", dma_chan_name(chan));

	buffer = kmalloc(1024 * sizeof(u8), GFP_KERNEL);

	par = kmalloc(sizeof(struct fbtft_par), GFP_KERNEL);
	par->spi = spi;

	dev->coherent_dma_mask = ~0;

	par->txbuf.buf = dmam_alloc_coherent(chan->device->dev,
	TX_BUFFER_SIZE * sizeof(u8), &par->txbuf.dma, GFP_KERNEL);

	if (!par->txbuf.buf) {
		ret = -ENOMEM;
		goto release;
	}
	par->txbuf.len = TX_BUFFER_SIZE;

//	par->info->screen_buffer = kmalloc(WIDTH * HEIGHT * sizeof(u16), GFP_KERNEL);
//
//	memset(par->info->screen_buffer, 0, WIDTH * HEIGHT * sizeof(u16));

	dev_info(&spi->dev, "my_probe start.\n");
	dev_info(&spi->dev, "master->bus_num = %d\n", spi->master->bus_num);
	dev_info(&spi->dev, "chip_select = %d\n", spi->chip_select);
	dev_info(&spi->dev, "bits_per_word = %d\n", spi->bits_per_word);

	led = devm_gpiod_get(&spi->dev, "led", GPIOD_OUT_HIGH);
	if (IS_ERR(led)) {
		pr_err("error \n");
		return -1;
	}
	par->gpio.reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(par->gpio.reset)) {
		pr_err("error \n");
		return -1;
	}
//
	par->gpio.dc = devm_gpiod_get(&spi->dev, "dc", GPIOD_OUT_HIGH);
	if (IS_ERR(par->gpio.dc)) {
		pr_err("error \n");
		return -1;
	}

	spin_lock_init(&par->dirty_lock);
	init_fb(dev);
	init_display();

	//Bit 3 must be  1 : RGB
	// Bit 4 :
	//Bit 7:
	write_reg(par, ILI9488_CMD_MEMORY_ACCESS_CONTROL, BIT(5) | BIT(3));

	fbtft_update_display(&spi->dev, par, 0, HEIGHT - 1);

	lcd_clear(&spi->dev, RED | GREEN);

//	fbtft_update_display2(&spi->dev, par);

//	write_reg(par, ILI9488_CMD_ALL_PIXELS_ON);
//
//
//	msleep(5000);

////	mdelay(1000);
	draw_rect(&spi->dev, RED, 0, 0, WIDTH, HEIGHT);
////	mdelay(1000);
//	draw_rect(&spi->dev, BLUE, 0, 0, 480, 160);
////	mdelay(1000);
//	draw_rect(&spi->dev, GREEN, 100, 100, 100, 100);
////	mdelay(1000);
//	draw_rect(&spi->dev, GREEN, 200, 100, 100, 1);
//	draw_hline(&spi->dev, RED, 0, 50, 480);
//	draw_vline(&spi->dev, RED | GREEN, 300, 0, 320);

	return ret;
	release: dma_release_channel(chan);
	return ret;
}

int my_remove(struct spi_device *spi) {
	dev_info(&spi->dev, "my_remove start.\n");

	unregister_framebuffer(info);
	fb_dealloc_cmap(&info->cmap);
	/* Reverse of framebuffer_alloc() */
	framebuffer_release(info);

	dma_release_channel(chan);
	kfree(buffer);
	dma_free_coherent(&spi->dev, TX_BUFFER_SIZE * sizeof(u8), par->txbuf.buf,
			par->txbuf.dma);
	kfree(par);
	return 0;
}

static const struct of_device_id tft9488_dt_ids[] = { { .compatible =
		"ilitek,ili9488" }, { }, };

MODULE_DEVICE_TABLE( of, tft9488_dt_ids);

static struct spi_driver spidev_spi_driver = { .driver = { .name =
		"ilitek,ili9488", .of_match_table = of_match_ptr(tft9488_dt_ids), },
		.probe = my_probe, .remove = my_remove, };

module_spi_driver( spidev_spi_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andy");
MODULE_DESCRIPTION("A sample c  driver");
